﻿
package extremefx.ui {
	import extremefx.data.DataSource;	
	
	/**
	 * @author Marcelo Volmaro
	 */
	public final class Virtual {
		private var _customSizes:Array;
		private var _ranges:Vector.<Range>;
		private var _defaultItemSize:uint;
		private var _ds:DataSource;
		
		public function setItemSize(pIndex:uint, pSize:uint):void {
			if (_customSizes[pIndex] == pSize) return;
			
			if (pSize){
				_customSizes[pIndex] = pSize;
				
			} else {
				delete _customSizes[pIndex];
			}
			
			_ranges = null;
		}
		
		public function getItemSize(pIndex:uint):uint {
			return _customSizes[pIndex] || _defaultItemSize;
		}
		
		/**
	     * Split the the position range into disjunct intervals. Each interval starts
	     * with a custom sized cell. Each position is contained in exactly one range.
	     * The ranges are sorted according to their start position.
	     * 
	     * Complexity: O(n log n) (n = number of custom sized cells)
	     * 
	     * @return the sorted list of ranges.
	     */ 
		private function _getRanges():Vector.<Range> {
			if (_ranges) return _ranges;
			
			var defaultSize:uint = _defaultItemSize,
				itemCount:uint = _ds.count;
				
			var indexes:Vector.<uint> = new Vector.<uint>();
			for each (var i:uint in _customSizes){
				indexes.push(i);
			}
			
			var ranges:Vector.<Range>;
			
			if (!indexes.length){
				ranges = _ranges = Vector.<Range>([new Range(0, itemCount-1, defaultSize, 0, itemCount * defaultSize - 1)]);
				return ranges;
			}
			
			indexes.sort(_vectorSort);
			
			ranges = new Vector.<Range>();
			var correctionSum:uint = 0, r:Range;
			
			for (i = 0; i < indexes.length; ++i){
				var index:uint = indexes[i];
		        if (index >= itemCount) {
		          break;
		        }
        
        		var cellSize:uint = _customSizes[index];
        		var rangeStart:uint = index * defaultSize + correctionSum;
        
		        correctionSum += cellSize - defaultSize;
		        
		        r = new Range(index, 0, cellSize, rangeStart, 0);         
		        
		        if (i > 0) {
		          r.rangeEnd = rangeStart-1;
		          r.endIndex = index-1;
		        }
		        
		        ranges.push(r); 
			}
			
			// fix first range
			r = ranges[0];
			if (r.rangeStart > 0) {
				ranges.unshift(new Range(0, r.startIndex-1, defaultSize, 0, r.rangeStart-1));   
			}
  
			// fix last range
			r = ranges[ranges.length-1];
			var remainingItemsSize:uint = (itemCount - r.startIndex - 1) * defaultSize;
			r.rangeEnd = r.rangeStart + r.firstItemSize + remainingItemsSize - 1;
			r.endIndex = itemCount - 1;
			 
			_ranges = ranges;
			return ranges;
		}
		
		private function _vectorSort(a:int, b:int):int {
			return a - b;
		}
		
		/**
	     * Returns the range, which contains the position
	     * 
	     * Complexity: O(log n) (n = number of custom sized cells)
	     * 
	     * @param pPosition the position
	     * @return The range, which contains the given position.
	     */
	    public function findRangeByPosition(pPosition:uint):Range {
			var ranges:Vector.<Range> = _ranges || _getRanges();
	      
			var start:uint = 0;
			var end:uint = ranges.length-1;
	      
			// binary search in the sorted ranges list
			while (true) {
				var pivot:uint = start + ((end - start) >> 1);
				var range:Range = ranges[pivot];
	        
				if (range.rangeEnd < pPosition) {
					start = pivot + 1;
					
				} else if (range.rangeStart > pPosition) {
					end = pivot - 1;
					
				} else {
					return range;
				}
			}
			
			return null;//fck flash compilter... Function does not return a value? It is not possible to exit from outside the loop....
		}
		
		/**
	     * Returns the range, which contains the position
	     * 
	     * Complexity: O(log n) (n = number of custom sized cells)
	     * 
	     * @param pIndex The index of the item to get the range for.
	     * @return The range for the index.
	     */
		private function _findRangeByIndex(pIndex:uint):Range {
			var ranges:Vector.<Range> = _ranges || _getRanges();
      
			var start:uint = 0;
			var end:uint = ranges.length-1;
      
			// binary search in the sorted ranges list
			while (true) {
				var pivot:uint = start + ((end - start) >> 1);
				var range:Range = ranges[pivot];
        
				if (range.endIndex < pIndex) {
					start = pivot + 1;
					
				} else if (range.startIndex > pIndex) {
					end = pivot - 1;
					
				} else {
					return range;
				}
			}
			
			return null;//fck flash compilter... Function does not return a value? It is not possible to exit from outside the loop....
		}
		
		/**
	     * Get the start position of the item with the given index.
	     * 
	     * @param pIndex The item's index
	     * @return The start position of the item. If the index is outside
	     *    of the axis range <code>-1</code> is returned
	     */ 
		public function getItemPosition(pIndex:uint):int {
			if (pIndex < 0 || pIndex >= _ds.count) {
				return -1;
			}
      
			var range:Range = _findRangeByIndex(pIndex);
      
			if (range.startIndex == pIndex) {
				return range.rangeStart;
				
			} else {
				return range.rangeStart + range.firstItemSize + (pIndex-range.startIndex-1) * _defaultItemSize;
			} 
		}
		
		/**
	     * Returns the sum of all cell sizes
	     * 
	     * @return The sum of all item sizes
	     */ 
		public function getTotalSize():uint {
			var ranges:Vector.<Range> = _ranges || _getRanges();
      		return ranges[ranges.length-1].rangeEnd + 1; 
		}
		
		/**
	     * Get an array of item sizes starting with the item at "pStartIndex". The
	     * sum of all sizes in the returned array is at least "pMinSizeSum". 
	     * 
	     * @param pStartIndex The index of the first item
	     * @param pMinSizeSum The minimum sum of the item sizes
	     * @return Array of item sizes starting with the size of the item
	     *    at index <code>pStartIndex</code>. The sum of the item sizes is at least
	     *    <code>pMinSizeSum</code>.
	     */ 
	     
	     public function getItemSizes(pStartIndex:uint, pMinSizeSum:uint):Vector.<uint> {
			var customSizes:Array = _customSizes,
				defaultSize:uint = _defaultItemSize,
				itemCount:uint = _ds.count;
      
			var sum:uint = 0;      
			var sizes:Vector.<uint> = new Vector.<uint>();
			
			while (sum < pMinSizeSum) {
				var itemSize:uint = customSizes[pStartIndex++] || defaultSize;
				sum += itemSize;
				sizes.push(itemSize);
				
				if (pStartIndex >= itemCount) {
					break;
				}
			}
			
			return sizes; 
	     }
	}
}


final class Range {
	public var startIndex:uint;
	public var endIndex:uint;
	public var firstItemSize:uint;
	public var rangeStart:uint;
	public var rangeEnd:uint;
	
	public function Range(pStartIndex:uint, pEndIndex:uint, pFis:uint, pRs:uint, pRe:uint) {
		startIndex = pStartIndex;
		endIndex = pEndIndex;
		firstItemSize = pFis;
		rangeStart = pRs;
		rangeEnd = pRe;
	}
}
